/*
--------------------------------------------------------------------------------
				Ak-Lib. 
			 Project       	: Ak-Lib
			 Group         	: Ak-Lib R&D Team
			 Author        	: Ocean.Wang
			 Create Date 	: 05-01-2010
--------------------------------------------------------------------------------
Code Name 	:  A.K 	~v~
Description	:  Yet Another Cross-Platform Lib.
Cautions    	:
--------------------------------------------------------------------------------
Change History:
Version		: 0.1
Building 		: 05/01/10
10-05.01 first draft, build, debug and test.
--------------------------------------------------------------------------------
*/

#include "ak_cli.h"
#include "ak_socket.h"

/* The ReadLine Interface */
#include "readline.h"
#include "history.h"

static 	int 	_gTraceLvl_Cli = TRACELVL_WARNING;

/* =====================================================================
** 	Application's Cli Module.
**	1. Delimeter: '\\', '/', "..", '.'
**
**	2. Basic Command: CD, LS, ?, Help, Quit, Exit
**
**	3. Baisc Flow of Cli.
**	3.1 Message & Route.
**	Each Msg Contains: IPAddr, AppPath, Command, Parameter.
**
**	3.2 Relay.
**	Each DevAgt Hook the CLI msg first, judge whether to Dispatch it to It's own Applets Or Relay It,
**	By Determine if the IPAddr is of It's own Addr List. 
**	If Dispatch, just route the Msg to Applet's Socket. When Applet Response, Route them to Cli.
**	If Relay, just route the Msg to the Relay Module, and Send to the DevAgt with the given IP Addr.
**
**	3.3 When a Command is Execute, an OutBuf & OutLen is given for Applet & Module to fill the result.
**
**	4. Line Edit & Parser.
**	Yacc/Lex, Bison/Flex will be Used to Gen the Parser Code.
**	Line Editor ? (Auto/Tab Complete Support) ReadLine.
*/
typedef struct{
	/* Network */
	unsigned short 		lPort; 		/* Local Port */
	unsigned short 		rPort;		/* Remote Relay Port */
	unsigned int 		rAddr;		/* Remote IpAddr */
	int 				socket;
	
	S_SOCKET_ADDRIN 	lSockAddr;	
	S_SOCKET_ADDRIN 	rSockAddr;	

	char 				remPath[S_APP_MAX_PATH + 1];
	char 				cliPath[S_APP_MAX_PATH + 1];

	int 				bCliConnect;
	int 				bCliDone;
}_t_cliCtx;

static 	_t_cliCtx 		_gCli_Ctx;

static 	int 	_SCli_SocketInit(_t_cliCtx * pCliCtx)
{
	int 				 i, ret;
	unsigned short 		 port;
	struct 	sockaddr_in *pSockAddr; 		

	/* Create Socket */
	pCliCtx->socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
	if(pCliCtx->socket == -1){
		SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR,  "CLI Socket Create Fail.\n");
		return -1;
	}
	S_SOCK_SET_NONBLOCK(pCliCtx->socket, ret);
	#if 1
	S_SOCK_SET_SNDBUF(pCliCtx->socket, ret, 0XC0000);		
	S_SOCK_SET_RCVBUF(pCliCtx->socket, ret, 0X400000);		
	#endif

	S_SOCK_SET_NOCHKSUM(pCliCtx->socket, ret);
	S_SOCK_SET_LOWDELAY(pCliCtx->socket, ret);

	/* Binding Socket */
	pSockAddr = &(pCliCtx->rSockAddr);
	SYS_MEMZERO(pSockAddr, sizeof(S_SOCKET_ADDRIN));
	pSockAddr->sin_family		= AF_INET;
	pSockAddr->sin_port			= htons(_S_CLI_PORT_SVR);
	pSockAddr->sin_addr.s_addr	= htonl(0X7F000001);

	pSockAddr = &(pCliCtx->lSockAddr);
	SYS_MEMZERO(pSockAddr, sizeof(S_SOCKET_ADDRIN));
	pSockAddr->sin_family		= AF_INET;
	pSockAddr->sin_addr.s_addr	= htonl(INADDR_ANY);
	
	for(i=0, ret=0, port=_S_CLI_PORT_CLT; i<_S_CLI_PORT_CNT; i++, port++){
		pSockAddr->sin_port		= htons(port);
		if(bind(pCliCtx->socket, (struct  sockaddr*)pSockAddr, sizeof(S_SOCKET_ADDRIN)) == -1){
			SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "CLI Socket Bind [%d] Fail.\n", port);
		}
		else{
			pCliCtx->lPort = port;
			ret = 1;
			break;
		}
	}
	if(ret == 0){
		/* Close Handle */
		SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "CLI Socket Port Not Avail.\n");
		SYS_CLOSE(close, pCliCtx->socket);
		return -1;
	}
	/* Init the Realy Addr & Port */
	pCliCtx->rPort = _S_CLI_PORT_SVR;
	pCliCtx->rAddr = htonl(0X7F000001);
		
	/* Init the CliPath & Flag */
	pCliCtx->cliPath[0]	= '/'; 
	pCliCtx->cliPath[1]	= '\0';
	pCliCtx->remPath[0]	= '/'; 
	pCliCtx->remPath[1]	= '\0';
	
	pCliCtx->bCliDone 	= 0;
	pCliCtx->bCliConnect	= 0;
	return 0;
}


/* =====================================================================
** 	Line Edit Module. (Using ReadLine)
*/

/* Strip whitespace from the start and end of STRING.  Return a pointer into STRING. */
static 	char * _SCli_StripSpace(char * string)
{
	register char *s, *t;

	/* Head */
	for (s = string; whitespace (*s); s++);
	if (*s == 0)
		return (s);

	/* Tail */
	t = s + SYS_STRLEN (s) - 1;
	while (t > s && whitespace (*t)){
		t--;
	}

	*++t = '\0';
	return s;
}

static 	char * 	_SCli_DupStr(char * s)
{
	char *r;
	r = SYS_MALLOC (SYS_STRLEN (s) + 1);
	if(r){
		strcpy (r, s);
	}
	return (r);
}


/* A structure which contains information on the commands this program can understand. */
typedef struct {
  char *name;				/* User printable name of the function. */
  int  (*func)(char *);		/* Function to call to do the job. */
  char *doc;				/* Documentation for this function.  */
} _t_cliCmd;

static 	int 	_SCliCmd_help (char * arg);
static 	int 	_SCliCmd_cd(char * arg);
static 	int 	_SCliCmd_ls(char * arg);
static 	int 	_SCliCmd_lcmd(char * arg);
static 	int 	_SCliCmd_pwd(char * arg);
static 	int 	_SCliCmd_conn(char * arg);
static 	int 	_SCliCmd_quit(char * arg);

static 	_t_cliCmd 	_gCli_CmdSys[] = {
	{ "?", 		_SCliCmd_help, 	"Show Help Info"},
	{ "help", 	_SCliCmd_help, 	"Show Help Info"},
	{ "cd", 	_SCliCmd_cd, 		"Change Dir"},
	{ "ls", 	_SCliCmd_ls, 		"List Dir"},
	{ "lcmd", 	_SCliCmd_lcmd, 	"List Command"},
	{ "pwd", 	_SCliCmd_pwd, 		"Current Working Dir"},
	{ "connect",_SCliCmd_conn, 	"Connect to FCast IP"},
	{ "quit", 	_SCliCmd_quit, 	"Quit"},
	{ "q", 		_SCliCmd_quit, 	"Quit"}
};


/* The names of functions that actually do the manipulation. */
static 	int 	_SCliCmd_help (char * arg)
{
	register int 	 i, count;
	_t_cliCmd * pCommand;
	int 			 printed = 0;

	count = _N_ARRAY(_gCli_CmdSys);
	for (i = 0, pCommand=_gCli_CmdSys; i<count; i++, pCommand++){
		if (!arg || !(*arg) || (strcmp(arg, pCommand->name) == 0)){
			SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "%s\t\t%s.\n", pCommand->name, pCommand->doc);
			printed++;
		}
	}

	return 0;
}

#define S_CLI_PATH_COPY(vDst, vSrc, vLen) \
	do{\
		vLen = SYS_STRLEN(vSrc);\
		strncpy(vDst, vSrc, vLen);\
		vDst[vLen] = '\0';\
	}while(0)
	
/* Change to the directory ARG. */
#define S_CLI_RSP_TO 	5000

static 	int 	_SCliCmd_RspStub(void 			* 	pArg, int lArg, 
									unsigned char 	* 	pMsg, 	int msgLen, 
									S_SOCKET_ADDRIN *pSockAddr)
{
	_t_cliCmdRsp * pRspCmd;
	_t_cliCmdHdr * pCmdHdr;
	_t_cliElemRsp*pRspElem;

	_t_sysMsgElem * pMsgElem;

	int 		ret, len;

	ret = 0;
	if(pMsg && (msgLen > 0)){
		pRspCmd = (_t_cliCmdRsp *)pMsg;
		pCmdHdr= &(pRspCmd->cmdHdr);
		
		pRspElem=(_t_cliElemRsp *)(pRspCmd->rspElem);
			
		pMsgElem = &(pRspElem->msgElem);
		len = pMsgElem->length - sizeof(_t_sysMsgElem);
		if(len > 0){
			fprintf(stdout, "%s\n", pRspElem->reqRsp);
		}else{
			fprintf(stdout, "no rsp.\n");
		}
		if(pMsgElem->type == _CLI_CMD_ERROR){
			ret = -1;
			S_CLI_PATH_COPY(_gCli_Ctx.cliPath, _gCli_Ctx.remPath, len);
		}

		return ret;
	}
	return -1;
}

static 	_t_cbentry 	_gCli_DefaultCB={
	(int (*)())_SCliCmd_RspStub,
	&_gCli_Ctx,
	0
};

static 	int 	_SCliCmd_ReqStub(_t_cliCtx * pCliCtx, 
								long 	cmdType, 
								char * 	strPath,
								char * 	strCmd, 
								char * 	strParam,
								_t_cbentry * pCbEntry)
{
	unsigned char 		msgBuf[S_CLI_MAX_MSG];
	_t_cliCmdReq 	*	pReqCmd;
	_t_cliElemReq*	pReqElem;
	_t_cliCmdHdr 	*	pCmdHdr;
	_t_sysMsgElem 	* 	pMsgElem;
	int 				ret, lenCmd, lenParam, lenPath;
	
	if(pCliCtx->socket == -1){
		SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "CLI Error Socket.\n");
		return -1;
	}

	if(pCliCtx->bCliConnect != 1){
		SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "CLI Not Connect.\n");
		return -1;
	}
	
	/* Judge the strCmd & strParam */
	if(!strCmd){
		SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "CLI NULL Command.\n");
		return -1;
	}
	
	/* Construct a New Message */
	pReqCmd = (_t_cliCmdReq *)msgBuf;
	pReqElem= pReqCmd->reqElem;
	pCmdHdr= &(pReqCmd->cmdHdr);
	pMsgElem= &(pReqElem->msgElem);
	
	lenCmd = SYS_STRLEN(strCmd);
	if(lenCmd >= S_CLI_MAX_CMD){
		SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "CLI Error Command:[%s].\n", strCmd);
		return -1;
	}
	strncpy(pReqElem->cmd, strCmd, lenCmd);
	pReqElem->cmd[lenCmd] = '\0';

	if(strParam){
		lenParam = SYS_STRLEN(strParam);
		if(lenParam >= S_CLI_MAX_PARAM){
			SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "CLI Error Param:[%s].\n", strParam);
			return -1;	
		}
		strncpy(pReqElem->param, strParam, lenParam);
		pReqElem->param[lenParam] = '\0';
	}else{
		pReqElem->param[0] = '\0';
	}

	if(strPath){
		lenPath = SYS_STRLEN(strPath);
		if(lenPath >= S_APP_MAX_PATH){
			SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "CLI Error Path:[%s].\n", strPath);
			return -1;
		}
		strncpy(pReqElem->path, pCliCtx->cliPath, lenPath);
		pReqElem->path[lenPath] = '\0';
	}
	else{
		pReqElem->path[0] = '/';
		pReqElem->path[0] = '\0';
	}
	
	pMsgElem->type 	= cmdType;
	pMsgElem->dir 	= _CLI_MSG_ELEM_REQ;
	pMsgElem->reserv= 0;
	pMsgElem->length= sizeof(_t_cliElemReq);

	CLI_CMDHDR_INIT(pCmdHdr, 
				pCliCtx->rAddr, 
				pCliCtx->rPort);
	pCmdHdr->length = pMsgElem->length + sizeof(_t_cliCmdHdr);
		
	/* Consume First */
	#if 0
	if(SockRx_Udp(pCliCtx->socket, NULL) == -1){
		SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "CLI Error Socket.\n");
		return -1;
	}
	#else
	SockRx_Udp(pCliCtx->socket, NULL);
	#endif

	/* Let's Send Out the Message */
	ret = SockTx_Udp(	pCliCtx->socket, msgBuf, pCmdHdr->length, &(pCliCtx->rSockAddr));

	if(ret == -1){
		SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "CLI Command:[%s] Send Fail.\n", strCmd);
		return -1;
	}

	/* Let's Wait for Response */
	ret = SockRxSel_Udp(pCliCtx->socket, pCbEntry, S_CLI_RSP_TO);
	if(ret == -1){
		SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "CLI Command:[%s] No Response.\n", strCmd);
		return -1;
	}
	return 0;	
}

static 	int 	_SCliCmd_cd (char * arg)
{
	int len, ret;
	/* Backup Path */
	S_CLI_PATH_COPY(_gCli_Ctx.remPath, _gCli_Ctx.cliPath, len);
	
	ret = SStrJ_Path(arg, 	_gCli_Ctx.cliPath);

	if(ret != -1){
		ret = _SCliCmd_ReqStub(&_gCli_Ctx, _CLI_CMD_CD, 
							_gCli_Ctx.cliPath,	"cd", NULL, 
							&_gCli_DefaultCB);
		if(ret == -1){
			/* Restore */
			S_CLI_PATH_COPY(_gCli_Ctx.cliPath, _gCli_Ctx.remPath, len);
		}
	}
	return ret;
}

/* List the file(s) named in arg. */
static int 		_SCliCmd_ls(char * arg)
{
	int len, ret;
	/* Backup Path */
	S_CLI_PATH_COPY(_gCli_Ctx.remPath, _gCli_Ctx.cliPath, len);

	ret = 0;
	if(arg[0] != '\0'){
		ret = SStrJ_Path(arg, 	_gCli_Ctx.cliPath);
	}

	if(ret != -1){
		ret = _SCliCmd_ReqStub(&_gCli_Ctx, _CLI_CMD_LS, 
						_gCli_Ctx.cliPath, "ls", NULL, 
						&_gCli_DefaultCB);
	}

	/* Restore */
	S_CLI_PATH_COPY(_gCli_Ctx.cliPath, _gCli_Ctx.remPath, len);
	
	return 0;
}

/* List the Command PWD. */
static int 		_SCliCmd_lcmd(char * arg)
{
	int len, ret;
	/* Backup Path */
	S_CLI_PATH_COPY(_gCli_Ctx.remPath, _gCli_Ctx.cliPath, len);
	
	ret = 0;
	if(arg[0] != '\0'){
		ret = SStrJ_Path(arg, 	_gCli_Ctx.cliPath);
	}

	if(ret != -1){
		ret = _SCliCmd_ReqStub(&_gCli_Ctx, _CLI_CMD_LCMD, 
						_gCli_Ctx.cliPath, "lcmd", NULL, 
						&_gCli_DefaultCB);
	}

	/* Restore */
	S_CLI_PATH_COPY(_gCli_Ctx.cliPath, _gCli_Ctx.remPath, len);
	
	return 0;
}

/* Print out the current working directory. */
static int 		_SCliCmd_pwd(char * arg)
{
	SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "%s\n", _gCli_Ctx.cliPath);
	return 0;
}

/* Quit the Cli Shell */
static int 		_SCliCmd_quit(char * arg)
{
	_gCli_Ctx.bCliDone = 1;
	return 0;
}

/* Connect to FCast */
static int 		_SCliCmd_conn(char * arg)
{
	int 				ret;
	unsigned int 		ipAddr;
	struct 	sockaddr_in *pSockAddr;
	
	/* Judge the IP Address */
	ipAddr = inet_addr(arg);
	if(ipAddr == 0){
		SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "Relay Addr:%s Invalid\n", arg);
		return -1;
	}
	pSockAddr = &(_gCli_Ctx.rSockAddr);
	pSockAddr->sin_addr.s_addr	= ipAddr;

	_gCli_Ctx.cliPath[0]	= '/'; 
	_gCli_Ctx.cliPath[1]	= '\0';
	_gCli_Ctx.remPath[0]	= '/'; 
	_gCli_Ctx.remPath[1]	= '\0';
	
	/* Connect */
	_gCli_Ctx.bCliConnect 		= 1;
	ret = _SCliCmd_ReqStub(&_gCli_Ctx, _CLI_CMD_CONNECT, 
					NULL, "connect", NULL, 
					&_gCli_DefaultCB);
	/* State Change */
	if(ret == -1){
		_gCli_Ctx.bCliConnect = 0;
	}
	return 0;
}

/* Custom Command */
static int 		_SCliCmd_custom(char * command, char * arg)
{
	_SCliCmd_ReqStub(&_gCli_Ctx, _CLI_CMD_EXEC, 
					_gCli_Ctx.cliPath, command, arg, 
					&_gCli_DefaultCB);
	return 0;
}

/* Look up NAME as the name of a command, and return a pointer to that command.  Return a NULL pointer if NAME isn't a command name. */
static 	_t_cliCmd * _SCliCmd_FindSys(char * name)
{
	register int 	 i, count;
	_t_cliCmd * pCommand;
	
	count = _N_ARRAY(_gCli_CmdSys);
	for (i = 0, pCommand = _gCli_CmdSys; i<count; i++, pCommand++){
		if (strcmp (name, pCommand->name) == 0)
			return pCommand;
	}

	return NULL;
}

static 	void 	_SCli_PrintLogo(void){
	fprintf(stdout, "==================================================\n");
	fprintf(stdout, "       Welcome to Ak-Lib's CLI             	   \n");
	fprintf(stdout, "==================================================\n");
	return;
}

/* 	Generator function for command completion.  STATE lets us know whether  to start from scratch; 
	without any state (i.e. STATE == 0), then we start at the top of the list. */
static 	int 	_gCli_FitIndex;
static 	int 	_gCli_FitLen;

static 	char * _SCliCmd_Gen(const char *text, int state)
{
	static int 			count;
	_t_cliCmd  * 	pCommand;

	if (!state){
		_gCli_FitIndex = 0;
		_gCli_FitLen 	= SYS_STRLEN (text);
	}

	/* Return the next name which partially matches from the command list. */
	count = _N_ARRAY(_gCli_CmdSys);
	pCommand = &_gCli_CmdSys[_gCli_FitIndex];
	
	while(_gCli_FitIndex<count){
		_gCli_FitIndex++;
		if (strncmp (pCommand->name, text, _gCli_FitLen) == 0){
			return _SCli_DupStr(pCommand->name);
		}
		pCommand++;
	}

	return (NULL);
}

/* 	Attempt to complete on the contents of TEXT. 
	START and END bound the region of rl_line_buffer that contains the word to complete.  
	TEXT is the word to complete.  
	We can use the entire contents of rl_line_buffer in case we want to do some simple parsing.  
	Return the array of matches,  or NULL if there aren't any. */
static 	char ** _SCli_Completion (const char *text, int start, int end)
{
  char **matches;

  matches = (char **)NULL;
  /* If this word is at the start of the line, then it is a command to complete.  
  Otherwise it is the name of a file in the current directory. */
  if (start == 0){
    	matches = rl_completion_matches (text, _SCliCmd_Gen);
  }
  return (matches);
}


/* 	Allow conditional parsing of the ~/.inputrc file. */
static 	void 	_SCli_ReadlineInit(void)
{
	_SCli_PrintLogo();
	
	rl_readline_name = "AkCLI";
	rl_catch_signals = 0;
	rl_attempted_completion_function = _SCli_Completion;
}

/* Execute a command line. */
static 	int		_SCli_Execline(char * line)
{
	register int 		i, flag, len;
	_t_cliCmd 	* 	pCommand;
	char 			* 	word;
	char 				cmdStr[S_CLI_MAX_CMD + 1];

	/* Isolate the command word. */
	i = flag = 0;
	while (line[i] && whitespace (line[i])){
		i++;
	}
	word = line + i;
	while (line[i] && !whitespace (line[i])){
		i++;
	}
	if (line[i]){
		line[i++] = '\0';
	}

	if(SStrJ_Avail(word)==-1){
		SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "CLI Cmd:[%s] Illegal.\n", word);
		return -1;
	}
	
	/* Get The Command */
	len = SYS_STRLEN(word);
	if(len >= S_CLI_MAX_CMD){
		SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "CLI Cmd:[%s] Illegal.\n", word);
		return -1;
	}
	strncpy(cmdStr, word, len);
	cmdStr[len] = '\0';

	/* Check If a System Command */
	pCommand = _SCliCmd_FindSys(word);
	if (!pCommand){
		SYS_TRACE(_gTraceLvl_Cli, TRACELVL_ERROR, "CLI Cmd:[%s].\n", word);
		flag = 1;
	}

	/* Get argument to command, if any. */
	while (whitespace (line[i])){
		i++;
	}
	word = line + i;

	if(flag == 0){
		return ((*(pCommand->func)) (word));
	}
	else{
		return _SCliCmd_custom(cmdStr, word);
	}
	return 0;
}

int 	main (int 	argc, char ** argv){

	char * 	progName;
	char *	line, *s;
	char 	path[S_APP_MAX_PATH + 1];
	
	progName = argv[0];

	_SCli_SocketInit(&_gCli_Ctx);
	_SCli_ReadlineInit();	/* Bind our completer. */
	while(!(_gCli_Ctx.bCliDone)){
		/* Adding the Path Info */
		SYS_SNPRINTF(path, S_APP_MAX_PATH, _CLI_FMT_PROMPT_PATH, _gCli_Ctx.cliPath);
		
		line = readline (path);
		if (!line)
			break;

		s = _SCli_StripSpace(line);
		if (*s){
			add_history(s);
			_SCli_Execline(s);
		}
		free(line);
	}
	
	exit (0);
}




